// Copyright (c) Microsoft. All rights reserved.

using System.Reflection;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Azure.EventHubs.Processor;
using Microsoft.Azure.IoTSolutions.AsaManager.DeviceGroupsAgent;
using Microsoft.Azure.IoTSolutions.AsaManager.Services.Concurrency;
using Microsoft.Azure.IoTSolutions.AsaManager.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.AsaManager.Services.EventHub;
using Microsoft.Azure.IoTSolutions.AsaManager.Services.Http;
using Microsoft.Azure.IoTSolutions.AsaManager.Services.Runtime;
using Microsoft.Azure.IoTSolutions.AsaManager.Services.Storage;
using Microsoft.Azure.IoTSolutions.AsaManager.WebService.Runtime;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Azure.IoTSolutions.AsaManager.WebService
{
    public static class DependencyResolution
    {
        /// <summary>
        /// Autofac configuration. Find more information here:
        /// @see http://docs.autofac.org/en/latest/integration/aspnetcore.html
        /// </summary>
        public static IContainer Setup(IServiceCollection services)
        {
            var builder = new ContainerBuilder();

            builder.Populate(services);

            AutowireAssemblies(builder);
            SetupCustomRules(builder);

            var container = builder.Build();
            RegisterFactory(container);

            return container;
        }

        /// <summary>
        /// Autowire interfaces to classes from all the assemblies, to avoid
        /// manual configuration. Note that autowiring works only for interfaces
        /// with just one implementation.
        /// @see http://autofac.readthedocs.io/en/latest/register/scanning.html
        /// </summary>
        private static void AutowireAssemblies(ContainerBuilder builder)
        {
            var assembly = Assembly.GetEntryAssembly();
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();

            // Auto-wire Services.DLL
            assembly = typeof(IServicesConfig).GetTypeInfo().Assembly;
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();

            // Auto-wire BlobStorage.DLL
            assembly = typeof(IBlobStorageConfig).GetTypeInfo().Assembly;
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();

            // Auto-wire SetupAgent.DLL
            assembly = typeof(SetupAgent.IAgent).GetTypeInfo().Assembly;
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();

            // Auto-wire DeviceGroupsAgent.DLL
            assembly = typeof(DeviceGroupsAgent.IAgent).GetTypeInfo().Assembly;
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();

            // Auto-wire TelemetryRulesAgent.DLL
            assembly = typeof(TelemetryRulesAgent.IAgent).GetTypeInfo().Assembly;
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
        }

        /// <summary>
        /// Setup custom rules overriding autowired ones, for example in cases
        /// where an interface has multiple implementations, and cases where
        /// a singleton is preferred to new instances.
        /// </summary>
        private static void SetupCustomRules(ContainerBuilder builder)
        {
            // Make sure the configuration is read only once.
            IConfig config = new Config(new ConfigData(new Logger(Uptime.ProcessId)));
            builder.RegisterInstance(config).As<IConfig>().SingleInstance();

            // Service configuration is generated by the entry point, so we
            // prepare the instance here.
            builder.RegisterInstance(config.LoggingConfig).As<ILoggingConfig>().SingleInstance();
            builder.RegisterInstance(config.ServicesConfig).As<IServicesConfig>().SingleInstance();
            builder.RegisterInstance(config.BlobStorageConfig).As<IBlobStorageConfig>().SingleInstance();

            // Instantiate only one logger
            var logger = new Logger(Uptime.ProcessId, config.LoggingConfig);
            builder.RegisterInstance(logger).As<ILogger>().SingleInstance();

            var threadWrapper = new ThreadWrapper();

            // Auth and CORS setup
            Auth.Startup.SetupDependencies(builder, config);

            // By default the DI container create new objects when injecting
            // dependencies. To improve performance we reuse some instances,
            // for example to reuse IoT Hub connections, as opposed to creating
            // a new connection every time.
            //builder.RegisterType<Foo>().As<IFoo>().SingleInstance();
            ICloudStorageWrapper cloudStorageWrapper = new CloudStoragWrapper();
            IBlobStorageHelper blobStorageHelper = new BlobStorageHelper(config.BlobStorageConfig, cloudStorageWrapper, logger);
            IFileWrapper fileWrapper = new FileWrapper();

            IDeviceGroupsWriter deviceGroupsWriter = new DeviceGroupsWriter(config.BlobStorageConfig, blobStorageHelper, fileWrapper, logger);
            builder.RegisterInstance(deviceGroupsWriter).As<IDeviceGroupsWriter>().SingleInstance();

            IHttpClient httpClient = new HttpClient(logger);
            IDevicesClient devicesClient = new DevicesClient(httpClient, config.ServicesConfig, logger);
            builder.RegisterInstance(devicesClient).As<IDevicesClient>().SingleInstance();
            IDeviceGroupsClient deviceGroupsClient = new DeviceGroupsClient(
                httpClient,
                devicesClient,
                config.ServicesConfig,
                logger,
                threadWrapper);
            builder.RegisterInstance(deviceGroupsClient).As<IDeviceGroupsClient>().SingleInstance();

            // Event Hub Classes
            IEventProcessorHostWrapper eventProcessorHostWrapper = new EventProcessorHostWrapper();
            builder.RegisterInstance(eventProcessorHostWrapper).As<IEventProcessorHostWrapper>().SingleInstance();
            IEventHubStatus eventHubStatus = new EventHubStatus();
            builder.RegisterInstance(eventHubStatus).As<IEventHubStatus>().SingleInstance();
            IEventProcessorFactory eventProcessorFactory = new DeviceEventProcessorFactory(eventHubStatus, config.ServicesConfig, logger);
            builder.RegisterInstance(eventProcessorFactory).As<IEventProcessorFactory>().SingleInstance();
        }

        private static void RegisterFactory(IContainer container)
        {
            Factory.RegisterResolver(container.Resolve);
        }
    }
}
